Completed
Pull Request — master (#101)
by thomas
01:01
created

APIClient.transaction   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
c 0
b 0
f 0
nc 1
dl 0
loc 3
rs 10
nop 1
1
/* globals onLoadWorkerLoadAsmCrypto */
2
3
var _ = require('lodash'),
4
    q = require('q'),
5
    bitcoin = require('bitcoinjs-lib'),
6
    bitcoinMessage = require('bitcoinjs-message'),
7
8
    bip39 = require("bip39"),
9
    Wallet = require('./wallet'),
10
    BtccomConverter = require('./btccom.convert'),
11
    BlocktrailConverter = require('./blocktrail.convert'),
12
    RestClient = require('./rest_client'),
13
    Encryption = require('./encryption'),
14
    KeyDerivation = require('./keyderivation'),
15
    EncryptionMnemonic = require('./encryption_mnemonic'),
16
    blocktrail = require('./blocktrail'),
17
    randomBytes = require('randombytes'),
18
    CryptoJS = require('crypto-js'),
19
    webworkifier = require('./webworkifier');
20
21
/**
22
 *
23
 * @param opt
24
 * @returns {*}
25
 */
26
function networkFromOptions(opt) {
27
    if (opt.bitcoinCash) {
28
        if (opt.regtest) {
29
            return bitcoin.networks.bitcoincashregtest;
30
        } else if (opt.testnet) {
31
            return bitcoin.networks.bitcoincashtestnet;
32
        } else {
33
            return bitcoin.networks.bitcoincash;
34
        }
35
    } else {
36
        if (opt.regtest) {
37
            return bitcoin.networks.regtest;
38
        } else if (opt.testnet) {
39
            return bitcoin.networks.testnet;
40
        } else {
41
            return bitcoin.networks.bitcoin;
42
        }
43
    }
44
}
45
46
var useWebWorker = require('./use-webworker')();
47
48
49
/**
50
 * helper to wrap a promise so that the callback get's called when it succeeds or fails
51
 *
52
 * @param promise   {q.Promise}
53
 * @param cb        function
54
 * @return q.Promise
55
 */
56
function callbackify(promise, cb) {
57
    // add a .then to trigger the cb for people using callbacks
58
    if (cb) {
59
        promise
60
            .then(function(res) {
61
                // use q.nextTick for asyncness
62
                q.nextTick(function() {
63
                    cb(null, res);
64
                });
65
            }, function(err) {
66
                // use q.nextTick for asyncness
67
                q.nextTick(function() {
68
                    cb(err, null);
69
                });
70
            });
71
    }
72
73
    // return the promise for people using promises
74
    return promise;
75
}
76
77
/**
78
 * Bindings to consume the BlockTrail API
79
 *
80
 * @param options       object{
81
 *                          apiKey: 'API_KEY',
82
 *                          apiSecret: 'API_SECRET',
83
 *                          host: 'defaults to api.blocktrail.com',
84
 *                          network: 'BTC|LTC',
85
 *                          testnet: true|false
86
 *                      }
87
 * @constructor
88
 */
89
var APIClient = function(options) {
90
    var self = this;
91
92
    // handle constructor call without 'new'
93
    if (!(this instanceof APIClient)) {
94
        return new APIClient(options);
95
    }
96
97
    var normalizedNetwork = APIClient.normalizeNetworkFromOptions(options);
98
    options.network = normalizedNetwork[0];
99
    options.testnet = normalizedNetwork[1];
100
    options.regtest = normalizedNetwork[2];
101
    // apiNetwork we allow to be customized for debugging purposes
102
    options.apiNetwork = options.apiNetwork || normalizedNetwork[3];
103
104
    self.bitcoinCash = options.network === "BCC";
105
    self.regtest = options.regtest;
106
    self.testnet = options.testnet;
107
    self.network = networkFromOptions(self);
108
    self.feeSanityCheck = typeof options.feeSanityCheck !== "undefined" ? options.feeSanityCheck : true;
109
    self.feeSanityCheckBaseFeeMultiplier = options.feeSanityCheckBaseFeeMultiplier || 200;
110
111
    options.apiNetwork = options.apiNetwork || ((self.testnet ? "t" : "") + (options.network || 'BTC').toUpperCase());
112
113
    if (typeof options.btccom === "undefined") {
114
        options.btccom = true;
115
    }
116
117
    /**
118
     * @type RestClient
119
     */
120
    self.dataClient = APIClient.initRestClient(_.merge({}, options));
121
    /**
122
     * @type RestClient
123
     */
124
    self.blocktrailClient = APIClient.initRestClient(_.merge({}, options, {btccom: false}));
125
126
    if (options.btccom) {
127
        self.converter = new BtccomConverter(self.network, true);
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
128
    } else {
129
        self.converter = new BlocktrailConverter();
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
130
    }
131
132
};
133
134
APIClient.normalizeNetworkFromOptions = function(options) {
135
    /* jshint -W071, -W074 */
136
    var network = 'BTC';
137
    var testnet = false;
138
    var regtest = false;
139
    var apiNetwork = "BTC";
0 ignored issues
show
Unused Code introduced by
The assignment to variable apiNetwork seems to be never used. Consider removing it.
Loading history...
140
141
    var prefix;
142
    var done = false;
143
144
    if (options.network) {
145
        var lower = options.network.toLowerCase();
146
147
        var m = lower.match(/^([rt])?(btc|bch|bcc)$/);
148
        if (!m) {
149
            throw new Error("Invalid network [" + options.network + "]");
150
        }
151
152
        if (m[2] === 'btc') {
153
            network = "BTC";
154
        } else {
155
            network = "BCC";
156
        }
157
158
        prefix = m[1];
159
        if (prefix) {
160
            // if there's a prefix then we're "done", won't apply options.regtest and options.testnet after
161
            done = true;
162
            if (prefix === 'r') {
163
                testnet = true;
164
                regtest = true;
165
            } else if (prefix === 't') {
166
                testnet = true;
167
            }
168
        }
169
    }
170
171
    // if we're not already done then apply options.regtest and options.testnet
172
    if (!done) {
173
        if (options.regtest) {
174
            testnet = true;
175
            regtest = true;
176
            prefix = "r";
177
        } else if (options.testnet) {
178
            testnet = true;
179
            prefix = "t";
180
        }
181
    }
182
183
    apiNetwork = (prefix || "") + network;
184
185
    return [network, testnet, regtest, apiNetwork];
186
};
187
188
APIClient.initRestClient = function(options) {
189
    // BLOCKTRAIL_SDK_API_ENDPOINT overwrite for development
190
    if (process.env.BLOCKTRAIL_SDK_API_ENDPOINT) {
191
        options.host = process.env.BLOCKTRAIL_SDK_API_ENDPOINT;
192
    }
193
194
    // trim off leading https?://
195
    if (options.host && options.host.indexOf("https://") === 0) {
196
        options.https = true;
197
        options.host = options.host.substr(8);
198
    } else if (options.host && options.host.indexOf("http://") === 0) {
199
        options.https = false;
200
        options.host = options.host.substr(7);
201
    }
202
203
    if (typeof options.https === "undefined") {
204
        options.https = true;
205
    }
206
207
    if (!options.port) {
208
        options.port = options.https ? 443 : 80;
209
    }
210
211
    if (options.btccom) {
212
        if (!options.host) {
213
            options.host = 'chain.api.btc.com';
214
        }
215
216
        if (!options.endpoint) {
217
            options.endpoint = "/" + (options.apiVersion || "v3");
218
        }
219
220
    } else {
221
        if (!options.host) {
222
            options.host = 'api.blocktrail.com';
223
        }
224
225
        if (!options.endpoint) {
226
            options.endpoint = "/" + (options.apiVersion || "v1") + (options.apiNetwork ? ("/" + options.apiNetwork) : "");
227
        }
228
    }
229
230
    return new RestClient(options);
231
};
232
233
var determineDataStorageV2_3 = function(options) {
234
    return q.when(options)
235
        .then(function(options) {
236
            // legacy
237
            if (options.storePrimaryMnemonic) {
238
                options.storeDataOnServer = options.storePrimaryMnemonic;
239
            }
240
241
            // storeDataOnServer=false when primarySeed is provided
242
            if (typeof options.storeDataOnServer === "undefined") {
243
                options.storeDataOnServer = !options.primarySeed;
244
            }
245
246
            return options;
247
        });
248
};
249
250
var produceEncryptedDataV2 = function(options, notify) {
251
    return q.when(options)
252
        .then(function(options) {
253
            if (options.storeDataOnServer) {
254
                if (!options.secret) {
255
                    if (!options.passphrase) {
256
                        throw new blocktrail.WalletCreateError("Can't encrypt data without a passphrase");
257
                    }
258
259
                    notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_SECRET);
260
261
                    options.secret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8).toString('hex'); // string because we use it as passphrase
262
                    options.encryptedSecret = CryptoJS.AES.encrypt(options.secret, options.passphrase).toString(CryptoJS.format.OpenSSL); // 'base64' string
263
                }
264
265
                notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_PRIMARY);
266
267
                options.encryptedPrimarySeed = CryptoJS.AES.encrypt(options.primarySeed.toString('base64'), options.secret)
268
                    .toString(CryptoJS.format.OpenSSL); // 'base64' string
269
                options.recoverySecret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8).toString('hex'); // string because we use it as passphrase
270
271
                notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_RECOVERY);
272
273
                options.recoveryEncryptedSecret = CryptoJS.AES.encrypt(options.secret, options.recoverySecret)
274
                                                              .toString(CryptoJS.format.OpenSSL); // 'base64' string
275
            }
276
277
            return options;
278
        });
279
};
280
281
APIClient.prototype.promisedEncrypt = function(pt, pw, iter) {
282
    if (useWebWorker && typeof onLoadWorkerLoadAsmCrypto === "function") {
283
        // generate randomness outside of webworker because many browsers don't have crypto.getRandomValues inside webworkers
284
        var saltBuf = Encryption.generateSalt();
285
        var iv = Encryption.generateIV();
286
287
        return webworkifier.workify(APIClient.prototype.promisedEncrypt, function factory() {
288
            return require('./webworker');
289
        }, onLoadWorkerLoadAsmCrypto, {
290
            method: 'Encryption.encryptWithSaltAndIV',
291
            pt: pt,
292
            pw: pw,
293
            saltBuf: saltBuf,
294
            iv: iv,
295
            iterations: iter
296
        })
297
            .then(function(data) {
298
                return Buffer.from(data.cipherText.buffer);
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
299
            });
300
    } else {
301
        try {
302
            return q.when(Encryption.encrypt(pt, pw, iter));
303
        } catch (e) {
304
            return q.reject(e);
305
        }
306
    }
307
};
308
309
APIClient.prototype.promisedDecrypt = function(ct, pw) {
310
    if (useWebWorker && typeof onLoadWorkerLoadAsmCrypto === "function") {
311
        return webworkifier.workify(APIClient.prototype.promisedDecrypt, function() {
312
            return require('./webworker');
313
        }, onLoadWorkerLoadAsmCrypto, {
314
            method: 'Encryption.decrypt',
315
            ct: ct,
316
            pw: pw
317
        })
318
            .then(function(data) {
319
                return Buffer.from(data.plainText.buffer);
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
320
            });
321
    } else {
322
        try {
323
            return q.when(Encryption.decrypt(ct, pw));
324
        } catch (e) {
325
            return q.reject(e);
326
        }
327
    }
328
};
329
330
APIClient.prototype.produceEncryptedDataV3 = function(options, notify) {
331
    var self = this;
332
333
    return q.when(options)
334
        .then(function(options) {
335
            if (options.storeDataOnServer) {
336
                return q.when()
337
                    .then(function() {
338
                        if (!options.secret) {
339
                            if (!options.passphrase) {
340
                                throw new blocktrail.WalletCreateError("Can't encrypt data without a passphrase");
341
                            }
342
343
                            notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_SECRET);
344
345
                            // -> now a buffer
346
                            options.secret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
347
348
                            // -> now a buffer
349
                            return self.promisedEncrypt(options.secret, new Buffer(options.passphrase), KeyDerivation.defaultIterations)
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
350
                                .then(function(encryptedSecret) {
351
                                    options.encryptedSecret = encryptedSecret;
352
                                });
353
                        } else {
354
                            if (!(options.secret instanceof Buffer)) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if !(options.secret instanceof Buffer) is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
355
                                throw new Error('Secret must be a buffer');
356
                            }
357
                        }
358
                    })
359
                    .then(function() {
360
                        notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_PRIMARY);
361
362
                        return self.promisedEncrypt(options.primarySeed, options.secret, KeyDerivation.subkeyIterations)
363
                            .then(function(encryptedPrimarySeed) {
364
                                options.encryptedPrimarySeed = encryptedPrimarySeed;
365
                            });
366
                    })
367
                    .then(function() {
368
                        // skip generating recovery secret when explicitly set to false
369
                        if (options.recoverySecret === false) {
370
                            return;
371
                        }
372
373
                        notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_RECOVERY);
374
                        if (!options.recoverySecret) {
375
                            options.recoverySecret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
376
                        }
377
378
                        return self.promisedEncrypt(options.secret, options.recoverySecret, KeyDerivation.defaultIterations)
379
                            .then(function(recoveryEncryptedSecret) {
380
                                options.recoveryEncryptedSecret = recoveryEncryptedSecret;
381
                            });
382
                    })
383
                    .then(function() {
384
                        return options;
385
                    });
386
            } else {
387
                return options;
388
            }
389
        });
390
};
391
392
var doRemainingWalletDataV2_3 = function(options, network, notify) {
393
    return q.when(options)
394
        .then(function(options) {
395
            if (!options.backupPublicKey) {
396
                options.backupSeed = options.backupSeed || randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
397
            }
398
399
            notify(APIClient.CREATE_WALLET_PROGRESS_PRIMARY);
400
401
            options.primaryPrivateKey = bitcoin.HDNode.fromSeedBuffer(options.primarySeed, network);
402
403
            notify(APIClient.CREATE_WALLET_PROGRESS_BACKUP);
404
405
            if (!options.backupPublicKey) {
406
                options.backupPrivateKey = bitcoin.HDNode.fromSeedBuffer(options.backupSeed, network);
407
                options.backupPublicKey = options.backupPrivateKey.neutered();
408
            }
409
410
            options.primaryPublicKey = options.primaryPrivateKey.deriveHardened(options.keyIndex).neutered();
411
412
            notify(APIClient.CREATE_WALLET_PROGRESS_SUBMIT);
413
414
            return options;
415
        });
416
};
417
418
APIClient.prototype.mnemonicToPrivateKey = function(mnemonic, passphrase, cb) {
419
    var self = this;
420
421
    var deferred = q.defer();
422
    deferred.promise.spreadNodeify(cb);
423
424
    deferred.resolve(q.fcall(function() {
425
        return self.mnemonicToSeedHex(mnemonic, passphrase).then(function(seedHex) {
426
            return bitcoin.HDNode.fromSeedHex(seedHex, self.network);
427
        });
428
    }));
429
430
    return deferred.promise;
431
};
432
433
APIClient.prototype.mnemonicToSeedHex = function(mnemonic, passphrase) {
434
    var self = this;
435
436
    if (useWebWorker) {
437
        return webworkifier.workify(self.mnemonicToSeedHex, function() {
438
            return require('./webworker');
439
        }, {method: 'mnemonicToSeedHex', mnemonic: mnemonic, passphrase: passphrase})
440
            .then(function(data) {
441
                return data.seed;
442
            });
443
    } else {
444
        try {
445
            return q.when(bip39.mnemonicToSeedHex(mnemonic, passphrase));
446
        } catch (e) {
447
            return q.reject(e);
448
        }
449
    }
450
};
451
452
APIClient.prototype.resolvePrimaryPrivateKeyFromOptions = function(options, cb) {
453
    var self = this;
454
455
    var deferred = q.defer();
456
    deferred.promise.nodeify(cb);
457
458
    try {
459
        // avoid conflicting options
460
        if (options.passphrase && options.password) {
461
            throw new blocktrail.WalletCreateError("Can't specify passphrase and password");
462
        }
463
        // normalize passphrase/password
464
        options.passphrase = options.passphrase || options.password;
465
        delete options.password;
466
467
        // avoid conflicting options
468
        if (options.primaryMnemonic && options.primarySeed) {
469
            throw new blocktrail.WalletInitError("Can only specify one of; Primary Mnemonic or Primary Seed");
470
        }
471
472
        // avoid deprecated options
473
        if (options.primaryPrivateKey) {
474
            throw new blocktrail.WalletInitError("Can't specify; Primary PrivateKey");
475
        }
476
477
        // make sure we have at least one thing to use
478
        if (!options.primaryMnemonic && !options.primarySeed) {
479
            throw new blocktrail.WalletInitError("Need to specify at least one of; Primary Mnemonic or Primary Seed");
480
        }
481
482
        if (options.primarySeed) {
483
            self.primarySeed = options.primarySeed;
484
            options.primaryPrivateKey = bitcoin.HDNode.fromSeedBuffer(self.primarySeed, self.network);
485
            deferred.resolve(options);
486
        } else {
487
            if (!options.passphrase) {
488
                throw new blocktrail.WalletInitError("Can't init wallet with Primary Mnemonic without a passphrase");
489
            }
490
491
            self.mnemonicToSeedHex(options.primaryMnemonic, options.passphrase)
492
                .then(function(seedHex) {
493
                    try {
494
                        options.primarySeed = new Buffer(seedHex, 'hex');
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
495
                        options.primaryPrivateKey = bitcoin.HDNode.fromSeedBuffer(options.primarySeed, self.network);
496
                        deferred.resolve(options);
497
                    } catch (e) {
498
                        deferred.reject(e);
499
                    }
500
                }, function(e) {
501
                    deferred.reject(e);
502
                });
503
        }
504
    } catch (e) {
505
        deferred.reject(e);
506
    }
507
508
    return deferred.promise;
509
};
510
511
APIClient.prototype.resolveBackupPublicKeyFromOptions = function(options, cb) {
512
    var self = this;
513
514
    var deferred = q.defer();
515
    deferred.promise.nodeify(cb);
516
517
    try {
518
        // avoid conflicting options
519
        if (options.backupMnemonic && options.backupPublicKey) {
520
            throw new blocktrail.WalletInitError("Can only specify one of; Backup Mnemonic or Backup PublicKey");
521
        }
522
523
        // make sure we have at least one thing to use
524
        if (!options.backupMnemonic && !options.backupPublicKey) {
525
            throw new blocktrail.WalletInitError("Need to specify at least one of; Backup Mnemonic or Backup PublicKey");
526
        }
527
528
        if (options.backupPublicKey) {
529
            if (options.backupPublicKey instanceof bitcoin.HDNode) {
530
                deferred.resolve(options);
531
            } else {
532
                options.backupPublicKey = bitcoin.HDNode.fromBase58(options.backupPublicKey, self.network);
533
                deferred.resolve(options);
534
            }
535
        } else {
536
            self.mnemonicToPrivateKey(options.backupMnemonic, "").then(function(backupPrivateKey) {
537
                options.backupPublicKey = backupPrivateKey.neutered();
538
                deferred.resolve(options);
539
            }, function(e) {
540
                deferred.reject(e);
541
            });
542
        }
543
    } catch (e) {
544
        deferred.reject(e);
545
    }
546
547
    return deferred.promise;
548
};
549
550
APIClient.prototype.debugAuth = function(cb) {
551
    var self = this;
552
553
    return self.dataClient.get("/debug/http-signature", null, true, cb);
554
};
555
556
/**
557
 * get a single address
558
 *
559
 * @param address      string       address hash
560
 * @param [cb]          function    callback function to call when request is complete
561
 * @return q.Promise
562
 */
563
APIClient.prototype.address = function(address, cb) {
564
    var self = this;
565
566
    return callbackify(self.dataClient.get(self.converter.getUrlForAddress(address), null)
567
        .then(function(data) {
568
            return self.converter.handleErros(self, data);
569
        })
570
        .then(function(data) {
571
            if (data === null) {
572
                return data;
573
            } else {
574
                return self.converter.convertAddress(data);
575
            }
576
        }), cb);
577
};
578
579
APIClient.prototype.addresses = function(addresses, cb) {
580
    var self = this;
581
582
    return callbackify(self.dataClient.post("/address", null, {"addresses": addresses}), cb);
583
};
584
585
586
/**
587
 * get all transactions for an address (paginated)
588
 *
589
 * @param address       string      address hash
590
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
591
 * @param [cb]          function    callback function to call when request is complete
592
 * @return q.Promise
593
 */
594
APIClient.prototype.addressTransactions = function(address, params, cb) {
595
596
    var self = this;
597
598
    if (typeof params === "function") {
599
        cb = params;
600
        params = null;
601
    }
602
603
    return callbackify(self.dataClient.get(self.converter.getUrlForAddressTransactions(address), self.converter.paginationParams(params))
604
        .then(function(data) {
605
            return self.converter.handleErros(self, data);
606
        })
607
        .then(function(data) {
608
            return data.data === null ? data : self.converter.convertAddressTxs(data);
609
        }), cb);
610
};
611
612
/**
613
 * get all transactions for a batch of addresses (paginated)
614
 *
615
 * @param addresses     array       address hashes
616
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
617
 * @param [cb]          function    callback function to call when request is complete
618
 * @return q.Promise
619
 */
620
APIClient.prototype.batchAddressHasTransactions = function(addresses, params, cb) {
621
    var self = this;
622
623
    if (typeof params === "function") {
624
        cb = params;
625
        params = null;
626
    }
627
628
    return self.dataClient.post("/address/has-transactions", self.converter.paginationParams(params), {"addresses": addresses}, cb);
629
};
630
631
/**
632
 * get all unconfirmed transactions for an address (paginated)
633
 *
634
 * @param address       string      address hash
635
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
636
 * @param [cb]          function    callback function to call when request is complete
637
 * @return q.Promise
638
 */
639
APIClient.prototype.addressUnconfirmedTransactions = function(address, params, cb) {
640
    var self = this;
641
642
    if (typeof params === "function") {
643
        cb = params;
644
        params = null;
645
    }
646
647
    return callbackify(self.dataClient.get(self.converter.getUrlForAddressTransactions(address), self.converter.paginationParams(params))
648
        .then(function(data) {
649
            return self.converter.handleErros(self, data);
650
        })
651
        .then(function(data) {
652
            if (data.data === null) {
653
                return data;
654
            }
655
656
            var res = self.converter.convertAddressTxs(data);
657
            res.data = res.data.filter(function(tx) {
658
                return !tx.confirmations;
659
            });
660
661
            return res;
662
        }), cb);
663
};
664
665
/**
666
 * get all unspent outputs for an address (paginated)
667
 *
668
 * @param address       string      address hash
669
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
670
 * @param [cb]          function    callback function to call when request is complete
671
 * @return q.Promise
672
 */
673
APIClient.prototype.addressUnspentOutputs = function(address, params, cb) {
674
    var self = this;
675
676
    if (typeof params === "function") {
677
        cb = params;
678
        params = null;
679
    }
680
681
    return callbackify(self.dataClient.get(self.converter.getUrlForAddressUnspent(address), self.converter.paginationParams(params))
682
        .then(function(data) {
683
            return self.converter.handleErros(self, data);
684
        })
685
        .then(function(data) {
686
            return data.data === null ? data : self.converter.convertAddressUnspentOutputs(data, address);
687
        }), cb);
688
};
689
690
/**
691
 * get all unspent outputs for a batch of addresses (paginated)
692
 *
693
 * @param addresses     array       address hashes
694
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
695
 * @param [cb]          function    callback function to call when request is complete
696
 * @return q.Promise
697
 */
698
APIClient.prototype.batchAddressUnspentOutputs = function(addresses, params, cb) {
699
    var self = this;
700
701
    if (self.converter instanceof BtccomConverter) {
702
        throw new Error("Not implemented");
703
    }
704
705
    if (typeof params === "function") {
706
        cb = params;
707
        params = null;
708
    }
709
710
    return callbackify(self.dataClient.post("/address/unspent-outputs", params, {"addresses": addresses}), cb);
711
};
712
713
/**
714
 * verify ownership of an address
715
 *
716
 * @param address       string      address hash
717
 * @param signature     string      a signed message (the address hash) using the private key of the address
718
 * @param [cb]          function    callback function to call when request is complete
719
 * @return q.Promise
720
 */
721
APIClient.prototype.verifyAddress = function(address, signature, cb) {
722
    var self = this;
723
724
    return self.verifyMessage(address, address, signature, cb);
725
};
726
727
/**
728
 *
729
 * get all blocks (paginated)
730
 * ASK
731
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
732
 * @param [cb]          function    callback function to call when request is complete
733
 * @return q.Promise
734
 */
735
APIClient.prototype.allBlocks = function(params, cb) {
736
    var self = this;
737
738
    if (self.converter instanceof BtccomConverter) {
739
        throw new Error("Not implemented");
740
    }
741
742
    if (typeof params === "function") {
743
        cb = params;
744
        params = null;
745
    }
746
747
    return callbackify(self.dataClient.get(self.converter.getUrlForAllBlocks(), self.converter.paginationParams(params)), cb);
748
};
749
750
/**
751
 * get a block
752
 *
753
 * @param block         string|int  a block hash or a block height
754
 * @param [cb]          function    callback function to call when request is complete
755
 * @return q.Promise
756
 */
757
APIClient.prototype.block = function(block, cb) {
758
    var self = this;
759
760
    return callbackify(self.dataClient.get(self.converter.getUrlForBlock(block), null)
761
        .then(function(data) {
762
            return self.converter.handleErros(self, data);
763
        })
764
        .then(function(data) {
765
            return data.data === null ? data : self.converter.convertBlock(data);
766
        }), cb);
767
};
768
769
/**
770
 * get the latest block
771
 *
772
 * @param [cb]          function    callback function to call when request is complete
773
 * @return q.Promise
774
 */
775
APIClient.prototype.blockLatest = function(cb) {
776
    var self = this;
777
778
    return callbackify(self.dataClient.get(self.converter.getUrlForBlock("latest"), null)
779
        .then(function(data) {
780
            return self.converter.handleErros(self, data);
781
        })
782
        .then(function(data) {
783
            return data.data === null ? data : self.converter.convertBlock(data);
784
        }), cb);
785
};
786
787
/**
788
 * get all transactions for a block (paginated)
789
 *
790
 * @param block         string|int  a block hash or a block height
791
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
792
 * @param [cb]          function    callback function to call when request is complete
793
 * @return q.Promise
794
 */
795
APIClient.prototype.blockTransactions = function(block, params, cb) {
796
    var self = this;
797
798
    if (typeof params === "function") {
799
        cb = params;
800
        params = null;
801
    }
802
803
    return callbackify(self.dataClient.get(self.converter.getUrlForBlockTransaction(block), self.converter.paginationParams(params))
804
        .then(function(data) {
805
            return self.converter.handleErros(self, data);
806
        })
807
        .then(function(data) {
808
            return data.data ===  null ? data : self.converter.convertBlockTxs(data);
809
        }), cb);
810
};
811
812
/**
813
 * get a single transaction
814
 *
815
 * @param tx            string      transaction hash
816
 * @param [cb]          function    callback function to call when request is complete
817
 * @return q.Promise
818
 */
819
APIClient.prototype.transaction = function(tx, cb) {
820
    var self = this;
821
822
    return callbackify(self.dataClient.get(self.converter.getUrlForTransaction(tx), null)
823
        .then(function(data) {
824
            return self.converter.handleErros(self, data);
825
        })
826
        .then(function(data) {
827
            if (data.data === null) {
828
                return data;
829
            } else {
830
                // for BTC.com API we need to fetch the raw hex from the BTC.com explorer endpoint
831
                if (self.converter instanceof BtccomConverter) {
832
                    var txPath = data.data.hash + ".rawhex";
833
                    return self.converter.specialRawTxClientOnly.get(txPath, null, false)
834
                        .then(function(rawTx) {
835
                            return [data, rawTx];
836
                        })
837
                        .then(function(dataAndTx) {
838
                            if (dataAndTx !== null) {
839
                                var data = dataAndTx[0];
840
                                var rawTx = dataAndTx[1];
841
                                return self.converter.convertTx(data, rawTx);
842
                            } else {
843
                                return dataAndTx;
844
                            }
845
                        });
846
                } else {
847
                    return self.converter.convertTx(data);
848
                }
849
            }
850
        }), cb);
851
};
852
853
/**
854
 * get a batch of transactions
855
 *
856
 * @param txs           string[]    list of transaction hashes (txId)
857
 * @param [cb]          function    callback function to call when request is complete
858
 * @return q.Promise
859
 */
860
APIClient.prototype.transactions = function(txs, cb) {
861
    var self = this;
862
863
    if (self.converter instanceof BtccomConverter) {
864
        return callbackify(self.dataClient.get(self.converter.getUrlForTransactions(txs), null)
865
            .then(function(data) {
866
                return self.converter.handleErros(self, data);
867
            })
868
            .then(function(data) {
869
                if (data.data === null) {
870
                    return data;
871
                } else {
872
                    return self.converter.convertTxs(data);
873
                }
874
            }), cb);
875
    } else {
876
        return callbackify(self.dataClient.post("/transactions", null, txs, null, false), cb);
877
    }
878
};
879
880
/**
881
 * get a paginated list of all webhooks associated with the api user
882
 *
883
 * @param [params]      object      pagination: {page: 1, limit: 20}
884
 * @param [cb]          function    callback function to call when request is complete
885
 * @return q.Promise
886
 */
887
APIClient.prototype.allWebhooks = function(params, cb) {
888
    var self = this;
889
890
    if (typeof params === "function") {
891
        cb = params;
892
        params = null;
893
    }
894
895
    return self.blocktrailClient.get("/webhooks", params, cb);
896
};
897
898
/**
899
 * create a new webhook
900
 *
901
 * @param url           string      the url to receive the webhook events
902
 * @param [identifier]  string      a unique identifier associated with the webhook
903
 * @param [cb]          function    callback function to call when request is complete
904
 * @return q.Promise
905
 */
906
APIClient.prototype.setupWebhook = function(url, identifier, cb) {
907
    var self = this;
908
909
    if (typeof identifier === "function") {
910
        //mimic function overloading
911
        cb = identifier;
912
        identifier = null;
913
    }
914
915
    return self.blocktrailClient.post("/webhook", null, {url: url, identifier: identifier}, cb);
916
};
917
918
/**
919
 * Converts a cash address to the legacy (base58) format
920
 * @param {string} input
921
 * @returns {string}
922
 */
923
APIClient.prototype.getLegacyBitcoinCashAddress = function(input) {
924
    if (this.network === bitcoin.networks.bitcoincash ||
925
        this.network === bitcoin.networks.bitcoincashtestnet ||
926
        this.network === bitcoin.networks.bitcoincashregtest) {
927
        var address;
928
        try {
929
            bitcoin.address.fromBase58Check(input, this.network);
930
            return input;
931
        } catch (e) {}
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
932
933
        address = bitcoin.address.fromCashAddress(input, this.network);
934
        var prefix;
935
        if (address.version === bitcoin.script.types.P2PKH) {
936
            prefix = this.network.pubKeyHash;
937
        } else if (address.version === bitcoin.script.types.P2SH) {
938
            prefix = this.network.scriptHash;
939
        } else {
940
            throw new Error("Unsupported address type");
941
        }
942
943
        return bitcoin.address.toBase58Check(address.hash, prefix);
944
    }
945
946
    throw new Error("Cash addresses only work on bitcoin cash");
947
};
948
949
/**
950
 * Converts a legacy bitcoin to the new cashaddr format
951
 * @param {string} input
952
 * @returns {string}
953
 */
954
APIClient.prototype.getCashAddressFromLegacyAddress = function(input) {
955
    if (this.network === bitcoin.networks.bitcoincash ||
956
        this.network === bitcoin.networks.bitcoincashtestnet ||
957
        this.network === bitcoin.networks.bitcoincashregtest
958
    ) {
959
        var address;
960
        try {
961
            bitcoin.address.fromCashAddress(input, this.network);
962
            return input;
963
        } catch (e) {}
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
964
965
        address = bitcoin.address.fromBase58Check(input, this.network);
966
        var scriptType;
967
        if (address.version === this.network.pubKeyHash) {
968
            scriptType = bitcoin.script.types.P2PKH;
969
        } else if (address.version === this.network.scriptHash) {
970
            scriptType = bitcoin.script.types.P2SH;
971
        } else {
972
            throw new Error("Unsupported address type");
973
        }
974
975
        return bitcoin.address.toCashAddress(address.hash, scriptType, this.network.cashAddrPrefix);
976
    }
977
978
    throw new Error("Cash addresses only work on bitcoin cash");
979
};
980
981
/**
982
 * get an existing webhook by it's identifier
983
 *
984
 * @param identifier    string      the unique identifier of the webhook to get
985
 * @param [cb]          function    callback function to call when request is complete
986
 * @return q.Promise
987
 */
988
APIClient.prototype.getWebhook = function(identifier, cb) {
989
    var self = this;
990
991
    return self.blocktrailClient.get("/webhook/" + identifier, null, cb);
992
};
993
994
/**
995
 * update an existing webhook
996
 *
997
 * @param identifier    string      the unique identifier of the webhook
998
 * @param webhookData   object      the data to update: {identifier: newIdentifier, url:newUrl}
999
 * @param [cb]          function    callback function to call when request is complete
1000
 * @return q.Promise
1001
 */
1002
APIClient.prototype.updateWebhook = function(identifier, webhookData, cb) {
1003
    var self = this;
1004
1005
    return self.blocktrailClient.put("/webhook/" + identifier, null, webhookData, cb);
1006
};
1007
1008
/**
1009
 * deletes an existing webhook and any event subscriptions associated with it
1010
 *
1011
 * @param identifier    string      the unique identifier of the webhook
1012
 * @param [cb]          function    callback function to call when request is complete
1013
 * @return q.Promise
1014
 */
1015
APIClient.prototype.deleteWebhook = function(identifier, cb) {
1016
    var self = this;
1017
1018
    return self.blocktrailClient.delete("/webhook/" + identifier, null, null, cb);
1019
};
1020
1021
/**
1022
 * get a paginated list of all the events a webhook is subscribed to
1023
 *
1024
 * @param identifier    string      the unique identifier of the webhook
1025
 * @param [params]      object      pagination: {page: 1, limit: 20}
1026
 * @param [cb]          function    callback function to call when request is complete
1027
 * @return q.Promise
1028
 */
1029
APIClient.prototype.getWebhookEvents = function(identifier, params, cb) {
1030
    var self = this;
1031
1032
    if (typeof params === "function") {
1033
        cb = params;
1034
        params = null;
1035
    }
1036
1037
    return self.blocktrailClient.get("/webhook/" + identifier + "/events", params, cb);
1038
};
1039
1040
/**
1041
 * subscribes a webhook to transaction events for a particular transaction
1042
 *
1043
 * @param identifier    string      the unique identifier of the webhook
1044
 * @param transaction   string      the transaction hash
1045
 * @param confirmations integer     the amount of confirmations to send
1046
 * @param [cb]          function    callback function to call when request is complete
1047
 * @return q.Promise
1048
 */
1049
APIClient.prototype.subscribeTransaction = function(identifier, transaction, confirmations, cb) {
1050
    var self = this;
1051
    var postData = {
1052
        'event_type': 'transaction',
1053
        'transaction': transaction,
1054
        'confirmations': confirmations
1055
    };
1056
1057
    return self.blocktrailClient.post("/webhook/" + identifier + "/events", null, postData, cb);
1058
};
1059
1060
/**
1061
 * subscribes a webhook to transaction events on a particular address
1062
 *
1063
 * @param identifier    string      the unique identifier of the webhook
1064
 * @param address       string      the address hash
1065
 * @param confirmations integer     the amount of confirmations to send
1066
 * @param [cb]          function    callback function to call when request is complete
1067
 * @return q.Promise
1068
 */
1069
APIClient.prototype.subscribeAddressTransactions = function(identifier, address, confirmations, cb) {
1070
    var self = this;
1071
    var postData = {
1072
        'event_type': 'address-transactions',
1073
        'address': address,
1074
        'confirmations': confirmations
1075
    };
1076
1077
    return self.blocktrailClient.post("/webhook/" + identifier + "/events", null, postData, cb);
1078
};
1079
1080
/**
1081
 * batch subscribes a webhook to multiple transaction events
1082
 *
1083
 * @param  identifier   string      the unique identifier of the webhook
1084
 * @param  batchData    array       An array of objects containing batch event data:
1085
 *                                  {address : 'address', confirmations : 'confirmations']
1086
 *                                  where address is the address to subscribe to and confirmations (optional) is the amount of confirmations to send
1087
 * @param [cb]          function    callback function to call when request is complete
1088
 * @return q.Promise
1089
 */
1090
APIClient.prototype.batchSubscribeAddressTransactions = function(identifier, batchData, cb) {
1091
    var self = this;
1092
    batchData.forEach(function(record) {
1093
        record.event_type = 'address-transactions';
1094
    });
1095
1096
    return self.blocktrailClient.post("/webhook/" + identifier + "/events/batch", null, batchData, cb);
1097
};
1098
1099
/**
1100
 * subscribes a webhook to a new block event
1101
 *
1102
 * @param identifier    string      the unique identifier of the webhook
1103
 * @param [cb]          function    callback function to call when request is complete
1104
 * @return q.Promise
1105
 */
1106
APIClient.prototype.subscribeNewBlocks = function(identifier, cb) {
1107
    var self = this;
1108
    var postData = {
1109
        'event_type': 'block'
1110
    };
1111
1112
    return self.blocktrailClient.post("/webhook/" + identifier + "/events", null, postData, cb);
1113
};
1114
1115
/**
1116
 * removes an address transaction event subscription from a webhook
1117
 *
1118
 * @param identifier    string      the unique identifier of the webhook
1119
 * @param address       string      the address hash
1120
 * @param [cb]          function    callback function to call when request is complete
1121
 * @return q.Promise
1122
 */
1123
APIClient.prototype.unsubscribeAddressTransactions = function(identifier, address, cb) {
1124
    var self = this;
1125
1126
    return self.blocktrailClient.delete("/webhook/" + identifier + "/address-transactions/" + address, null, null, cb);
1127
};
1128
1129
/**
1130
 * removes an transaction event subscription from a webhook
1131
 *
1132
 * @param identifier    string      the unique identifier of the webhook
1133
 * @param transaction   string      the transaction hash
1134
 * @param [cb]          function    callback function to call when request is complete
1135
 * @return q.Promise
1136
 */
1137
APIClient.prototype.unsubscribeTransaction = function(identifier, transaction, cb) {
1138
    var self = this;
1139
1140
    return self.blocktrailClient.delete("/webhook/" + identifier + "/transaction/" + transaction, null, null, cb);
1141
};
1142
1143
/**
1144
 * removes a block event subscription from a webhook
1145
 *
1146
 * @param identifier    string      the unique identifier of the webhook
1147
 * @param [cb]          function    callback function to call when request is complete
1148
 * @return q.Promise
1149
 */
1150
APIClient.prototype.unsubscribeNewBlocks = function(identifier, cb) {
1151
    var self = this;
1152
1153
    return self.blocktrailClient.delete("/webhook/" + identifier + "/block", null, null, cb);
1154
};
1155
1156
/**
1157
 * initialize an existing wallet
1158
 *
1159
 * Either takes two argument:
1160
 * @param options       object      {}
1161
 * @param [cb]          function    callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys)
1162
 *
1163
 * Or takes three arguments (old, deprecated syntax):
1164
 * @param identifier    string      the wallet identifier to be initialized
0 ignored issues
show
Documentation introduced by
The parameter identifier does not exist. Did you maybe forget to remove this comment?
Loading history...
1165
 * @param passphrase    string      the password to decrypt the mnemonic with
0 ignored issues
show
Documentation introduced by
The parameter passphrase does not exist. Did you maybe forget to remove this comment?
Loading history...
1166
 * @param [cb]          function    callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys)
0 ignored issues
show
Documentation introduced by
The parameter cb has already been documented on line 1161. The second definition is ignored.
Loading history...
1167
 *
1168
 * @returns {q.Promise}
1169
 */
1170
APIClient.prototype.initWallet = function(options, cb) {
1171
    var self = this;
1172
1173
    if (typeof options !== "object") {
1174
        // get the old-style arguments
1175
        options = {
1176
            identifier: arguments[0],
1177
            passphrase: arguments[1]
1178
        };
1179
1180
        cb = arguments[2];
1181
    }
1182
1183
    if (options.check_backup_key) {
1184
        if (typeof options.check_backup_key !== "string") {
1185
            throw new Error("Invalid input, must provide the backup key as a string (the xpub)");
1186
        }
1187
    }
1188
1189
    var deferred = q.defer();
1190
    deferred.promise.spreadNodeify(cb);
1191
1192
    var identifier = options.identifier;
1193
1194
    if (!identifier) {
1195
        deferred.reject(new blocktrail.WalletInitError("Identifier is required"));
1196
        return deferred.promise;
1197
    }
1198
1199
    deferred.resolve(self.blocktrailClient.get("/wallet/" + identifier, null, true).then(function(result) {
1200
        var keyIndex = options.keyIndex || result.key_index;
1201
1202
        options.walletVersion = result.wallet_version;
1203
1204
        if (options.check_backup_key) {
1205
            if (options.check_backup_key !== result.backup_public_key[0]) {
1206
                throw new Error("Backup key returned from server didn't match our own copy");
1207
            }
1208
        }
1209
        var backupPublicKey = bitcoin.HDNode.fromBase58(result.backup_public_key[0], self.network);
1210
        var blocktrailPublicKeys = _.mapValues(result.blocktrail_public_keys, function(blocktrailPublicKey) {
1211
            return bitcoin.HDNode.fromBase58(blocktrailPublicKey[0], self.network);
1212
        });
1213
        var primaryPublicKeys = _.mapValues(result.primary_public_keys, function(primaryPublicKey) {
1214
            return bitcoin.HDNode.fromBase58(primaryPublicKey[0], self.network);
1215
        });
1216
1217
        // initialize wallet
1218
        var wallet = new Wallet(
1219
            self,
1220
            identifier,
1221
            options.walletVersion,
1222
            result.primary_mnemonic,
1223
            result.encrypted_primary_seed,
1224
            result.encrypted_secret,
1225
            primaryPublicKeys,
1226
            backupPublicKey,
1227
            blocktrailPublicKeys,
1228
            keyIndex,
1229
            result.segwit || 0,
1230
            self.testnet,
1231
            self.regtest,
1232
            result.checksum,
1233
            result.upgrade_key_index,
1234
            options.useCashAddress,
1235
            options.bypassNewAddressCheck
1236
        );
1237
1238
        wallet.recoverySecret = result.recovery_secret;
1239
1240
        if (!options.readOnly) {
1241
            return wallet.unlock(options).then(function() {
1242
                return wallet;
1243
            });
1244
        } else {
1245
            return wallet;
1246
        }
1247
    }));
1248
1249
    return deferred.promise;
1250
};
1251
1252
APIClient.CREATE_WALLET_PROGRESS_START = 0;
1253
APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_SECRET = 4;
1254
APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_PRIMARY = 5;
1255
APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_RECOVERY = 6;
1256
APIClient.CREATE_WALLET_PROGRESS_PRIMARY = 10;
1257
APIClient.CREATE_WALLET_PROGRESS_BACKUP = 20;
1258
APIClient.CREATE_WALLET_PROGRESS_SUBMIT = 30;
1259
APIClient.CREATE_WALLET_PROGRESS_INIT = 40;
1260
APIClient.CREATE_WALLET_PROGRESS_DONE = 100;
1261
1262
/**
1263
 * create a new wallet
1264
 *   - will generate a new primary seed and backup seed
1265
 *
1266
 * Either takes two argument:
1267
 * @param options       object      {}
1268
 * @param [cb]          function    callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys)
1269
 *
1270
 * For v1 wallets (explicitly specify options.walletVersion=v1):
1271
 * @param options       object      {}
0 ignored issues
show
Documentation introduced by
The parameter options has already been documented on line 1267. The second definition is ignored.
Loading history...
1272
 * @param [cb]          function    callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys)
0 ignored issues
show
Documentation introduced by
The parameter cb has already been documented on line 1268. The second definition is ignored.
Loading history...
1273
 *
1274
 * Or takes four arguments (old, deprecated syntax):
1275
 * @param identifier    string      the wallet identifier to be initialized
0 ignored issues
show
Documentation introduced by
The parameter identifier does not exist. Did you maybe forget to remove this comment?
Loading history...
1276
 * @param passphrase    string      the password to decrypt the mnemonic with
0 ignored issues
show
Documentation introduced by
The parameter passphrase does not exist. Did you maybe forget to remove this comment?
Loading history...
1277
 * @param keyIndex      int         override for the blocktrail cosign key to use (for development purposes)
0 ignored issues
show
Documentation introduced by
The parameter keyIndex does not exist. Did you maybe forget to remove this comment?
Loading history...
1278
 * @param [cb]          function    callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys)
0 ignored issues
show
Documentation introduced by
The parameter cb has already been documented on line 1268. The second definition is ignored.
Loading history...
1279
 * @returns {q.Promise}
1280
 */
1281
APIClient.prototype.createNewWallet = function(options, cb) {
1282
    /* jshint -W071, -W074 */
1283
1284
    var self = this;
1285
1286
    if (typeof options !== "object") {
1287
        // get the old-style arguments
1288
        var identifier = arguments[0];
1289
        var passphrase = arguments[1];
1290
        var keyIndex = arguments[2];
1291
        cb = arguments[3];
1292
1293
        // keyIndex is optional
1294
        if (typeof keyIndex === "function") {
1295
            cb = keyIndex;
1296
            keyIndex = null;
1297
        }
1298
1299
        options = {
1300
            identifier: identifier,
1301
            passphrase: passphrase,
1302
            keyIndex: keyIndex
1303
        };
1304
    }
1305
1306
    // default to v3
1307
    options.walletVersion = options.walletVersion || Wallet.WALLET_VERSION_V3;
1308
1309
    var deferred = q.defer();
1310
    deferred.promise.spreadNodeify(cb);
1311
1312
    q.nextTick(function() {
1313
        deferred.notify(APIClient.CREATE_WALLET_PROGRESS_START);
1314
1315
        options.keyIndex = options.keyIndex || 0;
1316
        options.passphrase = options.passphrase || options.password;
1317
        delete options.password;
1318
1319
        if (!options.identifier) {
1320
            deferred.reject(new blocktrail.WalletCreateError("Identifier is required"));
1321
            return deferred.promise;
1322
        }
1323
1324
        if (options.walletVersion === Wallet.WALLET_VERSION_V1) {
1325
            self._createNewWalletV1(options)
1326
                .progress(function(p) { deferred.notify(p); })
1327
                .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); })
1328
            ;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1329
        } else if (options.walletVersion === Wallet.WALLET_VERSION_V2) {
1330
            self._createNewWalletV2(options)
1331
                .progress(function(p) { deferred.notify(p); })
1332
                .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); })
1333
            ;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1334
        } else if (options.walletVersion === Wallet.WALLET_VERSION_V3) {
1335
            self._createNewWalletV3(options)
1336
                .progress(function(p) { deferred.notify(p); })
1337
                .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); })
1338
            ;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1339
        } else {
1340
            deferred.reject(new blocktrail.WalletCreateError("Invalid wallet version!"));
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1341
        }
1342
    });
1343
1344
    return deferred.promise;
1345
};
1346
1347
APIClient.prototype._createNewWalletV1 = function(options) {
1348
    var self = this;
1349
1350
    var deferred = q.defer();
1351
1352
    q.nextTick(function() {
1353
1354
        if (!options.primaryMnemonic && !options.primarySeed) {
1355
            if (!options.passphrase && !options.password) {
1356
                deferred.reject(new blocktrail.WalletCreateError("Can't generate Primary Mnemonic without a passphrase"));
1357
                return deferred.promise;
1358
            } else {
1359
                options.primaryMnemonic = bip39.generateMnemonic(Wallet.WALLET_ENTROPY_BITS);
1360
                if (options.storePrimaryMnemonic !== false) {
1361
                    options.storePrimaryMnemonic = true;
1362
                }
1363
            }
1364
        }
1365
1366
        if (!options.backupMnemonic && !options.backupPublicKey) {
1367
            options.backupMnemonic = bip39.generateMnemonic(Wallet.WALLET_ENTROPY_BITS);
1368
        }
1369
1370
        deferred.notify(APIClient.CREATE_WALLET_PROGRESS_PRIMARY);
1371
1372
        self.resolvePrimaryPrivateKeyFromOptions(options)
1373
            .then(function(options) {
1374
                deferred.notify(APIClient.CREATE_WALLET_PROGRESS_BACKUP);
1375
1376
                return self.resolveBackupPublicKeyFromOptions(options)
1377
                    .then(function(options) {
1378
                        deferred.notify(APIClient.CREATE_WALLET_PROGRESS_SUBMIT);
1379
1380
                        // create a checksum of our private key which we'll later use to verify we used the right password
1381
                        var pubKeyHash = bitcoin.crypto.hash160(options.primaryPrivateKey.getPublicKeyBuffer());
1382
                        var checksum = bitcoin.address.toBase58Check(pubKeyHash, self.network.pubKeyHash);
1383
                        var keyIndex = options.keyIndex;
1384
1385
                        var primaryPublicKey = options.primaryPrivateKey.deriveHardened(keyIndex).neutered();
1386
1387
                        // send the public keys to the server to store them
1388
                        //  and the mnemonic, which is safe because it's useless without the password
1389
                        return self.storeNewWalletV1(
1390
                            options.identifier,
1391
                            [primaryPublicKey.toBase58(), "M/" + keyIndex + "'"],
1392
                            [options.backupPublicKey.toBase58(), "M"],
1393
                            options.storePrimaryMnemonic ? options.primaryMnemonic : false,
1394
                            checksum,
1395
                            keyIndex,
1396
                            options.segwit || null
1397
                        )
1398
                            .then(function(result) {
1399
                                deferred.notify(APIClient.CREATE_WALLET_PROGRESS_INIT);
1400
1401
                                var blocktrailPublicKeys = _.mapValues(result.blocktrail_public_keys, function(blocktrailPublicKey) {
1402
                                    return bitcoin.HDNode.fromBase58(blocktrailPublicKey[0], self.network);
1403
                                });
1404
1405
                                var wallet = new Wallet(
1406
                                    self,
1407
                                    options.identifier,
1408
                                    Wallet.WALLET_VERSION_V1,
1409
                                    options.primaryMnemonic,
1410
                                    null,
1411
                                    null,
1412
                                    {keyIndex: primaryPublicKey},
1413
                                    options.backupPublicKey,
1414
                                    blocktrailPublicKeys,
1415
                                    keyIndex,
1416
                                    result.segwit || 0,
1417
                                    self.testnet,
1418
                                    self.regtest,
1419
                                    checksum,
1420
                                    result.upgrade_key_index,
1421
                                    options.useCashAddress,
1422
                                    options.bypassNewAddressCheck
1423
                                );
1424
1425
                                return wallet.unlock({
1426
                                    walletVersion: Wallet.WALLET_VERSION_V1,
1427
                                    passphrase: options.passphrase,
1428
                                    primarySeed: options.primarySeed,
1429
                                    primaryMnemonic: null // explicit null
1430
                                }).then(function() {
1431
                                    deferred.notify(APIClient.CREATE_WALLET_PROGRESS_DONE);
1432
                                    return [
1433
                                        wallet,
1434
                                        {
1435
                                            walletVersion: wallet.walletVersion,
1436
                                            primaryMnemonic: options.primaryMnemonic,
1437
                                            backupMnemonic: options.backupMnemonic,
1438
                                            blocktrailPublicKeys: blocktrailPublicKeys
1439
                                        }
1440
                                    ];
1441
                                });
1442
                            });
1443
                    }
1444
                );
1445
            })
1446
            .then(
1447
            function(r) {
1448
                deferred.resolve(r);
1449
            },
1450
            function(e) {
1451
                deferred.reject(e);
1452
            }
1453
        )
1454
        ;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1455
    });
1456
1457
    return deferred.promise;
1458
};
1459
1460 View Code Duplication
APIClient.prototype._createNewWalletV2 = function(options) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1461
    var self = this;
1462
1463
    var deferred = q.defer();
1464
1465
    // avoid modifying passed options
1466
    options = _.merge({}, options);
1467
1468
    determineDataStorageV2_3(options)
1469
        .then(function(options) {
1470
            options.passphrase = options.passphrase || options.password;
1471
            delete options.password;
1472
1473
            // avoid deprecated options
1474
            if (options.primaryPrivateKey) {
1475
                throw new blocktrail.WalletInitError("Can't specify; Primary PrivateKey");
1476
            }
1477
1478
            // seed should be provided or generated
1479
            options.primarySeed = options.primarySeed || randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
1480
1481
            return options;
1482
        })
1483
        .then(function(options) {
1484
            return produceEncryptedDataV2(options, deferred.notify.bind(deferred));
1485
        })
1486
        .then(function(options) {
1487
            return doRemainingWalletDataV2_3(options, self.network, deferred.notify.bind(deferred));
1488
        })
1489
        .then(function(options) {
1490
            // create a checksum of our private key which we'll later use to verify we used the right password
1491
            var pubKeyHash = bitcoin.crypto.hash160(options.primaryPrivateKey.getPublicKeyBuffer());
1492
            var checksum = bitcoin.address.toBase58Check(pubKeyHash, self.network.pubKeyHash);
1493
            var keyIndex = options.keyIndex;
1494
1495
            // send the public keys and encrypted data to server
1496
            return self.storeNewWalletV2(
1497
                options.identifier,
1498
                [options.primaryPublicKey.toBase58(), "M/" + keyIndex + "'"],
1499
                [options.backupPublicKey.toBase58(), "M"],
1500
                options.storeDataOnServer ? options.encryptedPrimarySeed : false,
1501
                options.storeDataOnServer ? options.encryptedSecret : false,
1502
                options.storeDataOnServer ? options.recoverySecret : false,
1503
                checksum,
1504
                keyIndex,
1505
                options.support_secret || null,
1506
                options.segwit || null
1507
            )
1508
                .then(
1509
                function(result) {
1510
                    deferred.notify(APIClient.CREATE_WALLET_PROGRESS_INIT);
1511
1512
                    var blocktrailPublicKeys = _.mapValues(result.blocktrail_public_keys, function(blocktrailPublicKey) {
1513
                        return bitcoin.HDNode.fromBase58(blocktrailPublicKey[0], self.network);
1514
                    });
1515
1516
                    var wallet = new Wallet(
1517
                        self,
1518
                        options.identifier,
1519
                        Wallet.WALLET_VERSION_V2,
1520
                        null,
1521
                        options.storeDataOnServer ? options.encryptedPrimarySeed : null,
1522
                        options.storeDataOnServer ? options.encryptedSecret : null,
1523
                        {keyIndex: options.primaryPublicKey},
1524
                        options.backupPublicKey,
1525
                        blocktrailPublicKeys,
1526
                        keyIndex,
1527
                        result.segwit || 0,
1528
                        self.testnet,
1529
                        self.regtest,
1530
                        checksum,
1531
                        result.upgrade_key_index,
1532
                        options.useCashAddress,
1533
                        options.bypassNewAddressCheck
1534
                    );
1535
1536
                    // pass along decrypted data to avoid extra work
1537
                    return wallet.unlock({
1538
                        walletVersion: Wallet.WALLET_VERSION_V2,
1539
                        passphrase: options.passphrase,
1540
                        primarySeed: options.primarySeed,
1541
                        secret: options.secret
1542
                    }).then(function() {
1543
                        deferred.notify(APIClient.CREATE_WALLET_PROGRESS_DONE);
1544
                        return [
1545
                            wallet,
1546
                            {
1547
                                walletVersion: wallet.walletVersion,
1548
                                encryptedPrimarySeed: options.encryptedPrimarySeed ?
1549
                                    bip39.entropyToMnemonic(blocktrail.convert(options.encryptedPrimarySeed, 'base64', 'hex')) :
1550
                                    null,
1551
                                backupSeed: options.backupSeed ? bip39.entropyToMnemonic(options.backupSeed.toString('hex')) : null,
1552
                                recoveryEncryptedSecret: options.recoveryEncryptedSecret ?
1553
                                    bip39.entropyToMnemonic(blocktrail.convert(options.recoveryEncryptedSecret, 'base64', 'hex')) :
1554
                                    null,
1555
                                encryptedSecret: options.encryptedSecret ?
1556
                                    bip39.entropyToMnemonic(blocktrail.convert(options.encryptedSecret, 'base64', 'hex')) :
1557
                                    null,
1558
                                blocktrailPublicKeys: blocktrailPublicKeys
1559
                            }
1560
                        ];
1561
                    });
1562
                }
1563
            );
1564
        })
1565
       .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); });
1566
1567
    return deferred.promise;
1568
};
1569
1570 View Code Duplication
APIClient.prototype._createNewWalletV3 = function(options) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1571
    var self = this;
1572
1573
    var deferred = q.defer();
1574
1575
    // avoid modifying passed options
1576
    options = _.merge({}, options);
1577
1578
    determineDataStorageV2_3(options)
1579
        .then(function(options) {
1580
            options.passphrase = options.passphrase || options.password;
1581
            delete options.password;
1582
1583
            // avoid deprecated options
1584
            if (options.primaryPrivateKey) {
1585
                throw new blocktrail.WalletInitError("Can't specify; Primary PrivateKey");
1586
            }
1587
1588
            // seed should be provided or generated
1589
            options.primarySeed = options.primarySeed || randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
1590
1591
            return options;
1592
        })
1593
        .then(function(options) {
1594
            return self.produceEncryptedDataV3(options, deferred.notify.bind(deferred));
1595
        })
1596
        .then(function(options) {
1597
            return doRemainingWalletDataV2_3(options, self.network, deferred.notify.bind(deferred));
1598
        })
1599
        .then(function(options) {
1600
            // create a checksum of our private key which we'll later use to verify we used the right password
1601
            var pubKeyHash = bitcoin.crypto.hash160(options.primaryPrivateKey.getPublicKeyBuffer());
1602
            var checksum = bitcoin.address.toBase58Check(pubKeyHash, self.network.pubKeyHash);
1603
            var keyIndex = options.keyIndex;
1604
1605
            // send the public keys and encrypted data to server
1606
            return self.storeNewWalletV3(
1607
                options.identifier,
1608
                [options.primaryPublicKey.toBase58(), "M/" + keyIndex + "'"],
1609
                [options.backupPublicKey.toBase58(), "M"],
1610
                options.storeDataOnServer ? options.encryptedPrimarySeed : false,
1611
                options.storeDataOnServer ? options.encryptedSecret : false,
1612
                options.storeDataOnServer ? options.recoverySecret : false,
1613
                checksum,
1614
                keyIndex,
1615
                options.support_secret || null,
1616
                options.segwit || null
1617
            )
1618
                .then(
1619
                    // result, deferred, self(apiclient)
1620
                    function(result) {
1621
                        deferred.notify(APIClient.CREATE_WALLET_PROGRESS_INIT);
1622
1623
                        var blocktrailPublicKeys = _.mapValues(result.blocktrail_public_keys, function(blocktrailPublicKey) {
1624
                            return bitcoin.HDNode.fromBase58(blocktrailPublicKey[0], self.network);
1625
                        });
1626
1627
                        var wallet = new Wallet(
1628
                            self,
1629
                            options.identifier,
1630
                            Wallet.WALLET_VERSION_V3,
1631
                            null,
1632
                            options.storeDataOnServer ? options.encryptedPrimarySeed : null,
1633
                            options.storeDataOnServer ? options.encryptedSecret : null,
1634
                            {keyIndex: options.primaryPublicKey},
1635
                            options.backupPublicKey,
1636
                            blocktrailPublicKeys,
1637
                            keyIndex,
1638
                            result.segwit || 0,
1639
                            self.testnet,
1640
                            self.regtest,
1641
                            checksum,
1642
                            result.upgrade_key_index,
1643
                            options.useCashAddress,
1644
                            options.bypassNewAddressCheck
1645
                        );
1646
1647
                        // pass along decrypted data to avoid extra work
1648
                        return wallet.unlock({
1649
                            walletVersion: Wallet.WALLET_VERSION_V3,
1650
                            passphrase: options.passphrase,
1651
                            primarySeed: options.primarySeed,
1652
                            secret: options.secret
1653
                        }).then(function() {
1654
                            deferred.notify(APIClient.CREATE_WALLET_PROGRESS_DONE);
1655
                            return [
1656
                                wallet,
1657
                                {
1658
                                    walletVersion: wallet.walletVersion,
1659
                                    encryptedPrimarySeed: options.encryptedPrimarySeed ? EncryptionMnemonic.encode(options.encryptedPrimarySeed) : null,
1660
                                    backupSeed: options.backupSeed ? bip39.entropyToMnemonic(options.backupSeed) : null,
1661
                                    recoveryEncryptedSecret: options.recoveryEncryptedSecret ?
1662
                                        EncryptionMnemonic.encode(options.recoveryEncryptedSecret) : null,
1663
                                    encryptedSecret: options.encryptedSecret ? EncryptionMnemonic.encode(options.encryptedSecret) : null,
1664
                                    blocktrailPublicKeys: blocktrailPublicKeys
1665
                                }
1666
                            ];
1667
                        });
1668
                    }
1669
                );
1670
        })
1671
        .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); });
1672
1673
    return deferred.promise;
1674
};
1675
1676
function verifyPublicBip32Key(bip32Key, network) {
1677
    var hk = bitcoin.HDNode.fromBase58(bip32Key[0], network);
1678
    if (typeof hk.keyPair.d !== "undefined") {
1679
        throw new Error('BIP32Key contained private key material - abort');
1680
    }
1681
1682
    if (bip32Key[1].slice(0, 1) !== "M") {
1683
        throw new Error("BIP32Key contained non-public path - abort");
1684
    }
1685
}
1686
1687
function verifyPublicOnly(walletData, network) {
1688
    verifyPublicBip32Key(walletData.primary_public_key, network);
1689
    verifyPublicBip32Key(walletData.backup_public_key, network);
1690
}
1691
1692
/**
1693
 * create wallet using the API
1694
 *
1695
 * @param identifier            string      the wallet identifier to create
1696
 * @param primaryPublicKey      array       the primary public key - [key, path] should be M/<keyIndex>'
1697
 * @param backupPublicKey       array       the backup public key - [key, path] should be M/<keyIndex>'
1698
 * @param primaryMnemonic       string      mnemonic to store
1699
 * @param checksum              string      checksum to store
1700
 * @param keyIndex              int         keyIndex that was used to create wallet
1701
 * @param segwit                bool
1702
 * @returns {q.Promise}
1703
 */
1704
APIClient.prototype.storeNewWalletV1 = function(identifier, primaryPublicKey, backupPublicKey, primaryMnemonic,
1705
                                                checksum, keyIndex, segwit) {
1706
    var self = this;
1707
1708
    var postData = {
1709
        identifier: identifier,
1710
        wallet_version: Wallet.WALLET_VERSION_V1,
1711
        primary_public_key: primaryPublicKey,
1712
        backup_public_key: backupPublicKey,
1713
        primary_mnemonic: primaryMnemonic,
1714
        checksum: checksum,
1715
        key_index: keyIndex,
1716
        segwit: segwit
1717
    };
1718
1719
    verifyPublicOnly(postData, self.network);
1720
1721
    return self.blocktrailClient.post("/wallet", null, postData);
1722
};
1723
1724
/**
1725
 * create wallet using the API
1726
 *
1727
 * @param identifier            string      the wallet identifier to create
1728
 * @param primaryPublicKey      array       the primary public key - [key, path] should be M/<keyIndex>'
1729
 * @param backupPublicKey       array       the backup public key - [key, path] should be M/<keyIndex>'
1730
 * @param encryptedPrimarySeed  string      openssl format
1731
 * @param encryptedSecret       string      openssl format
1732
 * @param recoverySecret        string      openssl format
1733
 * @param checksum              string      checksum to store
1734
 * @param keyIndex              int         keyIndex that was used to create wallet
1735
 * @param supportSecret         string
1736
 * @param segwit                bool
1737
 * @returns {q.Promise}
1738
 */
1739
APIClient.prototype.storeNewWalletV2 = function(identifier, primaryPublicKey, backupPublicKey, encryptedPrimarySeed, encryptedSecret,
1740
                                                recoverySecret, checksum, keyIndex, supportSecret, segwit) {
1741
    var self = this;
1742
1743
    var postData = {
1744
        identifier: identifier,
1745
        wallet_version: Wallet.WALLET_VERSION_V2,
1746
        primary_public_key: primaryPublicKey,
1747
        backup_public_key: backupPublicKey,
1748
        encrypted_primary_seed: encryptedPrimarySeed,
1749
        encrypted_secret: encryptedSecret,
1750
        recovery_secret: recoverySecret,
1751
        checksum: checksum,
1752
        key_index: keyIndex,
1753
        support_secret: supportSecret || null,
1754
        segwit: segwit
1755
    };
1756
1757
    verifyPublicOnly(postData, self.network);
1758
1759
    return self.blocktrailClient.post("/wallet", null, postData);
1760
};
1761
1762
/**
1763
 * create wallet using the API
1764
 *
1765
 * @param identifier            string      the wallet identifier to create
1766
 * @param primaryPublicKey      array       the primary public key - [key, path] should be M/<keyIndex>'
1767
 * @param backupPublicKey       array       the backup public key - [key, path] should be M/<keyIndex>'
1768
 * @param encryptedPrimarySeed  Buffer      buffer of ciphertext
1769
 * @param encryptedSecret       Buffer      buffer of ciphertext
1770
 * @param recoverySecret        Buffer      buffer of recovery secret
1771
 * @param checksum              string      checksum to store
1772
 * @param keyIndex              int         keyIndex that was used to create wallet
1773
 * @param supportSecret         string
1774
 * @param segwit                bool
1775
 * @returns {q.Promise}
1776
 */
1777
APIClient.prototype.storeNewWalletV3 = function(identifier, primaryPublicKey, backupPublicKey, encryptedPrimarySeed, encryptedSecret,
1778
                                                recoverySecret, checksum, keyIndex, supportSecret, segwit) {
1779
    var self = this;
1780
1781
    var postData = {
1782
        identifier: identifier,
1783
        wallet_version: Wallet.WALLET_VERSION_V3,
1784
        primary_public_key: primaryPublicKey,
1785
        backup_public_key: backupPublicKey,
1786
        encrypted_primary_seed: encryptedPrimarySeed.toString('base64'),
1787
        encrypted_secret: encryptedSecret.toString('base64'),
1788
        recovery_secret: recoverySecret.toString('hex'),
1789
        checksum: checksum,
1790
        key_index: keyIndex,
1791
        support_secret: supportSecret || null,
1792
        segwit: segwit
1793
    };
1794
1795
    verifyPublicOnly(postData, self.network);
1796
1797
    return self.blocktrailClient.post("/wallet", null, postData);
1798
};
1799
1800
/**
1801
 * create wallet using the API
1802
 *
1803
 * @param identifier            string      the wallet identifier to create
1804
 * @param postData              object
1805
 * @param [cb]                  function    callback(err, result)
1806
 * @returns {q.Promise}
1807
 */
1808
APIClient.prototype.updateWallet = function(identifier, postData, cb) {
1809
    var self = this;
1810
1811
    return self.blocktrailClient.post("/wallet/" + identifier, null, postData, cb);
1812
};
1813
1814
/**
1815
 * upgrade wallet to use a new account number
1816
 *  the account number specifies which blocktrail cosigning key is used
1817
 *
1818
 * @param identifier            string      the wallet identifier
1819
 * @param primaryPublicKey      array       the primary public key - [key, path] should be M/<keyIndex>'
1820
 * @param keyIndex              int         keyIndex that was used to create wallet
1821
 * @param [cb]                  function    callback(err, result)
1822
 * @returns {q.Promise}
1823
 */
1824
APIClient.prototype.upgradeKeyIndex = function(identifier, keyIndex, primaryPublicKey, cb) {
1825
    var self = this;
1826
1827
    return self.blocktrailClient.post("/wallet/" + identifier + "/upgrade", null, {
1828
        key_index: keyIndex,
1829
        primary_public_key: primaryPublicKey
1830
    }, cb);
1831
};
1832
1833
/**
1834
 * get the balance for the wallet
1835
 *
1836
 * @param identifier            string      the wallet identifier
1837
 * @param [cb]                  function    callback(err, result)
1838
 * @returns {q.Promise}
1839
 */
1840
APIClient.prototype.getWalletBalance = function(identifier, cb) {
1841
    var self = this;
1842
1843
    return self.blocktrailClient.get("/wallet/" + identifier + "/balance", null, true, cb);
1844
};
1845
1846
/**
1847
 * get a new derivation number for specified parent path
1848
 *  eg; m/44'/1'/0/0 results in m/44'/1'/0/0/0 and next time in m/44'/1'/0/0/1 and next time in m/44'/1'/0/0/2
1849
 *
1850
 * @param identifier            string      the wallet identifier
1851
 * @param path                  string      the parent path for which to get a new derivation,
1852
 *                                           can be suffixed with /* to make it clear on which level the derivations hould be
1853
 * @param [cb]                  function    callback(err, result)
1854
 * @returns {q.Promise}
1855
 */
1856
APIClient.prototype.getNewDerivation = function(identifier, path, cb) {
1857
    var self = this;
1858
1859
    return self.blocktrailClient.post("/wallet/" + identifier + "/path", null, {path: path}, cb);
1860
};
1861
1862
1863
/**
1864
 * delete the wallet
1865
 *  the checksum address and a signature to verify you ownership of the key of that checksum address
1866
 *  is required to be able to delete a wallet
1867
 *
1868
 * @param identifier            string      the wallet identifier
1869
 * @param checksumAddress       string      the address for your master private key (and the checksum used when creating the wallet)
1870
 * @param checksumSignature     string      a signature of the checksum address as message signed by the private key matching that address
1871
 * @param [force]               bool        ignore warnings (such as a non-zero balance)
1872
 * @param [cb]                  function    callback(err, result)
1873
 * @returns {q.Promise}
1874
 */
1875
APIClient.prototype.deleteWallet = function(identifier, checksumAddress, checksumSignature, force, cb) {
1876
    var self = this;
1877
1878
    if (typeof force === "function") {
1879
        cb = force;
1880
        force = false;
1881
    }
1882
1883
    return self.blocktrailClient.delete("/wallet/" + identifier, {force: force}, {
1884
        checksum: checksumAddress,
1885
        signature: checksumSignature
1886
    }, cb);
1887
};
1888
1889
/**
1890
 * use the API to get the best inputs to use based on the outputs
1891
 *
1892
 * the return array has the following format:
1893
 * [
1894
 *  "utxos" => [
1895
 *      [
1896
 *          "hash" => "<txHash>",
1897
 *          "idx" => "<index of the output of that <txHash>",
1898
 *          "scriptpubkey_hex" => "<scriptPubKey-hex>",
1899
 *          "value" => 32746327,
1900
 *          "address" => "1address",
1901
 *          "path" => "m/44'/1'/0'/0/13",
1902
 *          "redeem_script" => "<redeemScript-hex>",
1903
 *      ],
1904
 *  ],
1905
 *  "fee"   => 10000,
1906
 *  "change"=> 1010109201,
1907
 * ]
1908
 *
1909
 * @param identifier        string      the wallet identifier
1910
 * @param pay               array       {'address': (int)value}     coins to send
1911
 * @param lockUTXO          bool        lock UTXOs for a few seconds to allow for transaction to be created
1912
 * @param allowZeroConf     bool        allow zero confirmation unspent outputs to be used in coin selection
1913
 * @param feeStrategy       string      defaults to
1914
 * @param options
1915
 * @param [cb]              function    callback(err, utxos, fee, change)
1916
 * @returns {q.Promise}
1917
 */
1918
APIClient.prototype.coinSelection = function(identifier, pay, lockUTXO, allowZeroConf, feeStrategy, options, cb) {
1919
    var self = this;
1920
1921
    if (typeof feeStrategy === "function") {
1922
        cb = feeStrategy;
1923
        feeStrategy = null;
1924
        options = {};
1925
    } else if (typeof options === "function") {
1926
        cb = options;
1927
        options = {};
1928
    }
1929
1930
    feeStrategy = feeStrategy || Wallet.FEE_STRATEGY_OPTIMAL;
1931
    options = options || {};
1932
1933
    var deferred = q.defer();
1934
    deferred.promise.spreadNodeify(cb);
1935
1936
    var params = {
1937
        lock: lockUTXO,
1938
        zeroconf: allowZeroConf ? 1 : 0,
1939
        zeroconfself: (typeof options.allowZeroConfSelf !== "undefined" ? options.allowZeroConfSelf : true) ? 1 : 0,
1940
        fee_strategy: feeStrategy
1941
    };
1942
1943
    if (options.forcefee) {
1944
        params['forcefee'] = options.forcefee;
1945
    }
1946
1947
    deferred.resolve(
1948
        self.blocktrailClient.post("/wallet/" + identifier + "/coin-selection", params, pay).then(
1949
            function(result) {
1950
                return [result.utxos, result.fee, result.change, result];
1951
            },
1952
            function(err) {
1953
                if (err.message.match(/too low to pay the fee/)) {
1954
                    throw blocktrail.WalletFeeError(err);
1955
                }
1956
1957
                throw err;
1958
            }
1959
        )
1960
    );
1961
1962
    return deferred.promise;
1963
};
1964
1965
/**
1966
 * @param [cb]              function    callback(err, utxos, fee, change)
1967
 * @returns {q.Promise}
1968
 */
1969
APIClient.prototype.feePerKB = function(cb) {
1970
    var self = this;
1971
1972
    var deferred = q.defer();
1973
    deferred.promise.spreadNodeify(cb);
1974
1975
    deferred.resolve(self.blocktrailClient.get("/fee-per-kb"));
1976
1977
    return deferred.promise;
1978
};
1979
1980
/**
1981
 * send the transaction using the API
1982
 *
1983
 * @param identifier        string      the wallet identifier
1984
 * @param txHex             string      partially signed transaction as hex string
1985
 * @param paths             array       list of paths used in inputs which should be cosigned by the API
1986
 * @param checkFee          bool        when TRUE the API will verify if the fee is 100% correct and otherwise throw an exception
1987
 * @param [twoFactorToken]  string      2FA token
1988
 * @param [prioboost]       bool
1989
 * @param [cb]              function    callback(err, txHash)
1990
 * @returns {q.Promise}
1991
 */
1992
APIClient.prototype.sendTransaction = function(identifier, txHex, paths, checkFee, twoFactorToken, prioboost, cb) {
1993
    var self = this;
1994
1995
    if (typeof twoFactorToken === "function") {
1996
        cb = twoFactorToken;
1997
        twoFactorToken = null;
1998
        prioboost = false;
1999
    } else if (typeof prioboost === "function") {
2000
        cb = prioboost;
2001
        prioboost = false;
2002
    }
2003
2004
    var data = {
2005
        paths: paths,
2006
        two_factor_token: twoFactorToken
2007
    };
2008
    if (typeof txHex === "string") {
2009
        data.raw_transaction = txHex;
2010
    } else if (typeof txHex === "object") {
2011
        Object.keys(txHex).map(function(key) {
2012
            data[key] = txHex[key];
2013
        });
2014
    }
2015
2016
    return self.blocktrailClient.post(
2017
        "/wallet/" + identifier + "/send",
2018
        {
2019
            check_fee: checkFee ? 1 : 0,
2020
            prioboost: prioboost ? 1 : 0
2021
        },
2022
        data,
2023
        cb
2024
    );
2025
};
2026
2027
/**
2028
 * setup a webhook for this wallet
2029
 *
2030
 * @param identifier        string      the wallet identifier
2031
 * @param webhookIdentifier string      identifier for the webhook
2032
 * @param url               string      URL to receive webhook events
2033
 * @param [cb]              function    callback(err, webhook)
2034
 * @returns {q.Promise}
2035
 */
2036
APIClient.prototype.setupWalletWebhook = function(identifier, webhookIdentifier, url, cb) {
2037
    var self = this;
2038
2039
    return self.blocktrailClient.post("/wallet/" + identifier + "/webhook", null, {url: url, identifier: webhookIdentifier}, cb);
2040
};
2041
2042
/**
2043
 * delete a webhook that was created for this wallet
2044
 *
2045
 * @param identifier        string      the wallet identifier
2046
 * @param webhookIdentifier string      identifier for the webhook
2047
 * @param [cb]              function    callback(err, success)
2048
 * @returns {q.Promise}
2049
 */
2050
APIClient.prototype.deleteWalletWebhook = function(identifier, webhookIdentifier, cb) {
2051
    var self = this;
2052
2053
    return self.blocktrailClient.delete("/wallet/" + identifier + "/webhook/" + webhookIdentifier, null, null, cb);
2054
};
2055
2056
/**
2057
 * get all transactions for an wallet (paginated)
2058
 *
2059
 * @param identifier    string      wallet identifier
2060
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
2061
 * @param [cb]          function    callback function to call when request is complete
2062
 * @return q.Promise
2063
 */
2064
APIClient.prototype.walletTransactions = function(identifier, params, cb) {
2065
    var self = this;
2066
2067
    if (typeof params === "function") {
2068
        cb = params;
2069
        params = null;
2070
    }
2071
2072
    return self.blocktrailClient.get("/wallet/" + identifier + "/transactions", params, true, cb);
2073
};
2074
2075
/**
2076
 * get all addresses for an wallet (paginated)
2077
 *
2078
 * @param identifier    string      wallet identifier
2079
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
2080
 * @param [cb]          function    callback function to call when request is complete
2081
 * @return q.Promise
2082
 */
2083
APIClient.prototype.walletAddresses = function(identifier, params, cb) {
2084
    var self = this;
2085
2086
    if (typeof params === "function") {
2087
        cb = params;
2088
        params = null;
2089
    }
2090
2091
    return self.blocktrailClient.get("/wallet/" + identifier + "/addresses", params, true, cb);
2092
};
2093
2094
/**
2095
 * @param identifier    string      wallet identifier
2096
 * @param address       string      the address to label
2097
 * @param label         string      the label
2098
 * @param [cb]          function    callback(err, res)
2099
 * @return q.Promise
2100
 */
2101
APIClient.prototype.labelWalletAddress = function(identifier, address, label, cb) {
2102
    var self = this;
2103
2104
    return self.blocktrailClient.post("/wallet/" + identifier + "/address/" + address + "/label", null, {label: label}, cb);
2105
};
2106
2107
APIClient.prototype.walletMaxSpendable = function(identifier, allowZeroConf, feeStrategy, options, cb) {
2108
    var self = this;
2109
2110
    if (typeof feeStrategy === "function") {
2111
        cb = feeStrategy;
2112
        feeStrategy = null;
2113
    } else if (typeof options === "function") {
2114
        cb = options;
2115
        options = {};
2116
    }
2117
2118
    feeStrategy = feeStrategy || Wallet.FEE_STRATEGY_OPTIMAL;
2119
    options = options || {};
2120
2121
    var params = {
2122
        outputs: options.outputs ? options.outputs : 1,
2123
        zeroconf: allowZeroConf ? 1 : 0,
2124
        zeroconfself: (typeof options.allowZeroConfSelf !== "undefined" ? options.allowZeroConfSelf : true) ? 1 : 0,
2125
        fee_strategy: feeStrategy
2126
    };
2127
2128
    if (options.forcefee) {
2129
        params['forcefee'] = options.forcefee;
2130
    }
2131
2132
    return self.blocktrailClient.get("/wallet/" + identifier + "/max-spendable", params, true, cb);
2133
};
2134
2135
/**
2136
 * get all UTXOs for an wallet (paginated)
2137
 *
2138
 * @param identifier    string      wallet identifier
2139
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
2140
 * @param [cb]          function    callback function to call when request is complete
2141
 * @return q.Promise
2142
 */
2143
APIClient.prototype.walletUTXOs = function(identifier, params, cb) {
2144
    var self = this;
2145
2146
    if (typeof params === "function") {
2147
        cb = params;
2148
        params = null;
2149
    }
2150
2151
    return self.blocktrailClient.get("/wallet/" + identifier + "/utxos", params, true, cb);
2152
};
2153
2154
/**
2155
 * get a paginated list of all wallets associated with the api user
2156
 *
2157
 * @param [params]      object      pagination: {page: 1, limit: 20}
2158
 * @param [cb]          function    callback function to call when request is complete
2159
 * @return q.Promise
2160
 */
2161
APIClient.prototype.allWallets = function(params, cb) {
2162
    var self = this;
2163
2164
    if (typeof params === "function") {
2165
        cb = params;
2166
        params = null;
2167
    }
2168
2169
    return self.blocktrailClient.get("/wallets", params, true, cb);
2170
};
2171
2172
/**
2173
 * verify a message signed bitcoin-core style
2174
 *
2175
 * @param message        string
2176
 * @param address        string
2177
 * @param signature      string
2178
 * @param [cb]          function    callback function to call when request is complete
2179
 * @return q.Promise
2180
 */
2181
APIClient.prototype.verifyMessage = function(message, address, signature, cb) {
2182
    var self = this;
2183
2184
    var deferred = q.defer();
2185
    deferred.promise.nodeify(cb);
2186
    try {
2187
        var result = bitcoinMessage.verify(address, self.network.messagePrefix, message, new Buffer(signature, 'base64'));
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
2188
        deferred.resolve(result);
2189
    } catch (e) {
2190
        deferred.reject(e);
2191
    }
2192
2193
    return deferred.promise;
2194
};
2195
2196
/**
2197
 * max is 0.001
2198
 * testnet only
2199
 *
2200
 * @param address
2201
 * @param amount
2202
 * @param cb
2203
 */
2204
APIClient.prototype.faucetWithdrawl = function(address, amount, cb) {
2205
    var self = this;
2206
2207
    return self.blocktrailClient.post("/faucet/withdrawl", null, {address: address, amount: amount}, cb);
2208
};
2209
2210
/**
2211
 * send a raw transaction
2212
 *
2213
 * @param rawTransaction    string      raw transaction as HEX
2214
 * @param [cb]              function    callback function to call when request is complete
2215
 * @return q.Promise
2216
 */
2217
APIClient.prototype.sendRawTransaction = function(rawTransaction, cb) {
2218
    var self = this;
2219
2220
    return self.blocktrailClient.post("/send-raw-tx", null, rawTransaction, cb);
2221
};
2222
2223
/**
2224
 * get the current price index
2225
 *
2226
 * @param [cb]          function    callback({'USD': 287.30})
2227
 * @return q.Promise
2228
 */
2229
APIClient.prototype.price = function(cb) {
2230
    var self = this;
2231
2232
    return self.blocktrailClient.get("/price", null, false, cb);
2233
};
2234
2235
module.exports = APIClient;
2236